import * as ansi from 'tui-lib/util/ansi'
import unic from 'tui-lib/util/unichars'

import {DisplayElement} from 'tui-lib/ui/primitives'

export default class ScrollBar extends DisplayElement {
  constructor({
    getLayoutType,
    getCurrentScroll,
    getMaximumScroll,
    getTotalItems
  }) {
    super()

    this.getLayoutType = getLayoutType
    this.getCurrentScroll = getCurrentScroll
    this.getMaximumScroll = getMaximumScroll
    this.getTotalItems = getTotalItems
  }

  fixLayout() {
    // Normally we'd subtract one from contentW/contentH when setting the x/y
    // position, but the scroll-bar is actually displayed OUTSIDE of (adjacent
    // to) the parent's content area.
    if (this.getLayoutType() === 'vertical') {
      this.h = this.parent.contentH
      this.w = 1
      this.x = this.parent.contentW
      this.y = 0
    } else {
      this.h = 1
      this.w = this.parent.contentW
      this.x = 0
      this.y = this.parent.contentH
    }
  }

  drawTo(writable) {
    // Uuuurgh
    this.fixLayout()

    // TODO: Horizontal layout! Not functionally a lot different, but I'm too
    // lazy to write a test UI for it right now.

    const {
      backwards: canScrollBackwards,
      forwards: canScrollForwards
    } = this.getScrollableDirections()

    // - 2 for extra UI elements (arrows)
    const totalLength = this.h - 2

    // ..[-----]..
    //   ^start|
    //         ^end
    //
    // Start and end should correspond to how much of the scroll area
    // is currently visible. So, if you can see 60% of the full scroll length
    // at a time, and you are scrolled 10% down, the start position of the
    // handle should be 10% down, and it should extend 60% of the scrollbar
    // length, to the 70% mark.

    // NB: I think this math mixes the units for "items" and "lines".
    // edgeLength is measured in lines, while totalItems is a number of items.
    // This isn't a problem when the length of an item is equal to one line,
    // but it's still worth investigating at some point.
    const currentScroll = this.getCurrentScroll()
    const totalItems = this.getTotalItems()
    const edgeLength = this.parent.contentH
    const visibleAtOnce = Math.min(totalItems, edgeLength)
    const handleLength = visibleAtOnce / totalItems * totalLength
    let handlePosition = Math.floor(totalLength / totalItems * currentScroll)

    // Silly peeve of mine: The handle should only be visibly touching the top
    // or bottom of the scrollbar area if you're actually scrolled all the way
    // to the start or end. Otherwise, it shouldn't be touching! There should
    // visible space indicating that you can scroll in that direction
    // (in addition to the arrows we show at the ends).

    if (canScrollBackwards && handlePosition === 0) {
      handlePosition = 1
    }

    if (canScrollForwards && (handlePosition + handleLength) === edgeLength) {
      handlePosition--
    }

    if (this.getLayoutType() === 'vertical') {
      const start = this.absTop + handlePosition + 1
      for (let i = 0; i < handleLength; i++) {
        writable.write(ansi.moveCursor(start + i, this.absLeft))
        writable.write(unic.BOX_V_DOUBLE)
      }

      if (canScrollBackwards) {
        writable.write(ansi.moveCursor(this.absTop, this.absLeft))
        writable.write(unic.ARROW_UP_DOUBLE)
      }

      if (canScrollForwards) {
        writable.write(ansi.moveCursor(this.absBottom, this.absLeft))
        writable.write(unic.ARROW_DOWN_DOUBLE)
      }
    }
  }

  getScrollableDirections() {
    const currentScroll = this.getCurrentScroll()
    const maximumScroll = this.getMaximumScroll()

    return {
      backwards: (currentScroll > 0),
      forwards: (currentScroll < maximumScroll)
    }
  }

  canScrollAtAll() {
    const {backwards, forwards} = this.getScrollableDirections()
    return backwards || forwards
  }
}
